﻿using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Lztkdr.FileTransfer.Event;
using ZetaLongPaths;

namespace Lztkdr.FileTransfer
{
    /// <summary>
    /// 单文件 断点续传 类
    /// 外部注册，内部调用事件：OnStart，OnUpdateProgress，OnError，OnMd5Checking，OnCompleted
    /// </summary>
    public class FileTransfer
    {
        /// <summary>
        /// 数据包大小(默认16mb)
        /// </summary>
        public long PackSize { get; set; } = 1024 * 1024 * 16;

        /// <summary>
        /// 标记 PackSize索引 的补齐位数
        /// 最大文件Size = 16 * 999,999
        /// </summary>
        public static readonly int PadLength = 6;

        /// <summary>
        /// 字符串的标记长度
        /// </summary>
        public static int MarkLength
        {
            get
            {
                return PadLength * 2 + 1;// 开始长度-总长度
            }
        }

        public event EventHandler<OnStartEventArgs> OnStart;

        public event EventHandler<OnUpdateProgressEventArgs> OnUpdateProgress;

        public event EventHandler<OnErrorEventArgs> OnError;

        public event EventHandler<OnMd5CheckingEventArgs> OnMd5Checking;

        public event EventHandler<OnCompletedEventArgs> OnCompleted;

        #region 属性

        /// <summary>
        /// 工作状态
        /// </summary>
        public State State { get; set; }

        /// <summary>
        /// 文件源路径
        /// </summary>
        public ZlpFileInfo SrcFileInfo { get; private set; }

        /// <summary>
        /// 文件的目标路径
        /// </summary>
        public ZlpFileInfo TgtFileInfo { get; private set; }


        /// <summary>
        /// 文件的目标目录
        /// </summary>
        public string TgtDir { get; private set; }

        /// <summary>
        /// 文件大小
        /// </summary>
        public long FileLength { get; private set; }

        /// <summary>
        /// 需传输包的次数
        /// </summary>
        public int PackCount { get; private set; }

        /// <summary>
        /// 总进度：默认 PackCount*2
        /// 分片占100，合并分片占100,校验文件占1
        /// </summary>
        public int MaxProgress { get; private set; }


        /// <summary>
        /// 进度百分比
        /// </summary>
        public decimal CurrRate
        {
            get
            {
                var re = Math.Round(((this.CurrPackPosIndex + 1) * 1.000 / this.MaxProgress * 1.000), 3);
                return (decimal)(re > 1 ? 1 : re);
            }
        }

        public object UserState
        {
            get;
            set;
        }

        /// <summary>
        /// 传输任务
        /// </summary>
        public Task TFTask { get; set; }


        public bool Md5Check { get; private set; }

        /// <summary>
        /// 是否是拷贝？
        /// true：拷贝，源文件会保留
        /// false:迁移，源文件会删除
        /// </summary>
        public bool IsCopy { get; set; }

        /// <summary>
        /// 是否忙碌(正在执行)
        /// </summary>
        public bool IsBusy { set; private get; }

        #endregion


        public FileValidator Validator { get; set; }


        #region 构造函数

        /// <summary>
        /// 断点续传
        /// </summary>
        /// <param name="userState">用户状态数据</param>
        /// <param name="srcPath">源文件路径</param>
        /// <param name="tgtPath">目标文件路径</param>
        /// <param name="packSize">每次传输包大小：默认 16mb </param>
        /// <param name="isCopy">是否拷贝</param>
        /// <param name="md5Check">处理完成后，是否md5校验</param>
        public FileTransfer(object userState, string srcPath, string tgtPath, long packSize = 1024 * 1024 * 16, bool isCopy = true, bool md5Check = true)
        {
            this.UserState = userState;
            this.SrcFileInfo = new ZlpFileInfo(srcPath);
            this.TgtFileInfo = new ZlpFileInfo(tgtPath);
            this.PackSize = packSize;
            this.IsCopy = isCopy;
            this.Md5Check = md5Check;
            this.State = State.等待;

            this.Validator = new FileValidator(this, srcPath, tgtPath);

            Init();
        }

        private void Init()
        {
            if (this.SrcFileInfo.Exists)
            {
                this.TgtDir = this.TgtFileInfo.DirectoryName;
                ZlpDirectoryInfo dirInfo = new ZlpDirectoryInfo(this.TgtDir);
                if (!dirInfo.Exists)
                {
                    dirInfo.Create();
                }

                this.FileLength = this.SrcFileInfo.Length;

                if ((this.FileLength % this.PackSize) > 0)
                {
                    this.PackCount = (int)(this.FileLength / this.PackSize) + 1;
                }
                else
                {
                    this.PackCount = (int)(this.FileLength / this.PackSize);
                }
                // 最大进度 = 分配的包总个数 + Md5分配
                this.MaxProgress = this.PackCount * 1;
            }
        }

        #endregion

        /// <summary>
        /// 开始执行
        /// </summary>
        public void Start()
        {
            this.OnStart?.Invoke(this, new OnStartEventArgs(this.UserState));
            RunWorkerAsync();
        }

        /// <summary>
        /// 取消
        /// </summary>
        public Task CancelAsync()
        {
            this.State = State.取消;

            while (true)
            {
                if (this.IsBusy || this.TFTask.Status != TaskStatus.RanToCompletion)
                {
                    Thread.Sleep(500);
                }
                else
                {
                    break;
                }
            }

            return Task.Run(() =>
            {
                try
                {
                    this.TFTask.Wait();
                }
                catch (Exception ex)
                {
                    this.OnError?.Invoke(this, new OnErrorEventArgs(nameof(CancelAsync), ex, this.UserState));
                }
                DeleteOver();
                this.State = State.取消;
                this._CurrPackPosIndex = -1;
                this.OnCompleted?.Invoke(this, new OnCompletedEventArgs(this.UserState, null));
            });
        }

        /// <summary>
        /// 暂停
        /// </summary>
        public Task PauseAsync()
        {
            this.State = State.暂停;
            while (true)
            {
                if (this.IsBusy || this.TFTask.Status != TaskStatus.RanToCompletion)
                {
                    Thread.Sleep(500);
                }
                else
                {
                    break;
                }
            }

            return Task.Run(() =>
            {
                try
                {
                    this.TFTask.Wait();
                }
                catch (Exception ex)
                {
                    this.OnError?.Invoke(this, new OnErrorEventArgs(nameof(PauseAsync), ex, this.UserState));
                }
                this.State = State.暂停;
            });
        }

        /// <summary>
        /// 继续
        /// </summary>
        public Task ContinceAsync()
        {
            while (true)
            {
                if (this.IsBusy || this.TFTask.Status != TaskStatus.RanToCompletion)
                {
                    Thread.Sleep(500);
                }
                else
                {
                    break;
                }
            }

            return Task.Run(() =>
            {
                try
                {
                    this.TFTask.Wait();
                }
                catch (Exception ex)
                {
                    this.OnError?.Invoke(this, new OnErrorEventArgs(nameof(ContinceAsync), ex, this.UserState));
                }

                if (this.CurrRate >= 1) // this.CurrRate 已经走 CurrPosIndex 执行流程，也通过Md5检验，则完成
                {
                    if (!Validator.IsSame())
                    {
                        RunWorkerAsync();
                    }
                    else
                    {
                        this.State = State.完成;
                        this.OnCompleted?.Invoke(this, new OnCompletedEventArgs(this.UserState, true));
                    }

                    if (!this.IsCopy)
                    {
                        DeleteOver();
                    }
                }
                else
                {
                    //this.OnUpdateProgress?.Invoke(this, new OnUpdateProgressEventArgs(this.UserState, this.CurrRate));
                    RunWorkerAsync();
                }
            });
        }

        /// <summary>
        /// 重做
        /// </summary>
        public Task RedoAsync()
        {
            //重做，需要先取消，再重做
            this.State = State.取消;

            while (true)
            {
                if (this.IsBusy || this.TFTask.Status != TaskStatus.RanToCompletion)
                {
                    Thread.Sleep(500);
                }
                else
                {
                    break;
                }
            }

            return Task.Run(() =>
            {
                try
                {
                    this.TFTask.Wait();
                }
                catch (Exception ex)
                {
                    this.OnError?.Invoke(this, new OnErrorEventArgs(nameof(RedoAsync), ex, this.UserState));
                }
                DeleteOver();
                this.State = State.取消;

                this._CurrPackPosIndex = -1;
                this.OnUpdateProgress?.Invoke(this, new OnUpdateProgressEventArgs(this.UserState, 0));
                RunWorkerAsync();
            });
        }


        private string GeneratePos(object index)
        {
            string res = index.ToString().PadLeft(PadLength, '0') + "-" + this.PackCount.ToString().PadLeft(PadLength, '0');
            return res;
        }

        private long _CurrPackPosIndex = 0;
        public long CurrPackPosIndex
        {
            get
            {
                if (_CurrPackPosIndex <= -1)//即使 文件大小相同，md5值相同，也会从0开始
                {
                    return 0;
                }

                if (_CurrPackPosIndex == 0)
                {
                    if (this.TgtFileInfo.Exists)
                    {
                        //检测 目标文件 是否进行 断电续传
                        if (TgtFileInfo.Length > 0)
                        {
                            if (TgtFileInfo.Length == SrcFileInfo.Length)//md5 比对文件，文件一致则跳过
                            {
                                if (Md5Compare()) //下面会对 md5 检验
                                {
                                    _CurrPackPosIndex = this.PackCount;
                                }
                                else //文件 虽然大小一致，md5 不一样 ， 则从头开始写入
                                {
                                    _CurrPackPosIndex = 0;
                                }
                                return _CurrPackPosIndex;
                            }
                            else
                            {
                                if (TgtFileInfo.Length > SrcFileInfo.Length)//待迁移或待拷贝的 文件 比源文件 还大， 则从头开始写入
                                {
                                    _CurrPackPosIndex = 0;
                                    return _CurrPackPosIndex;
                                }

                                FileStream fs = null;
                                try
                                {
                                    fs = new FileStream(this.TgtFileInfo.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                                    if (fs.Length <= 0)
                                    {
                                        _CurrPackPosIndex = 0;
                                        return _CurrPackPosIndex;
                                    }
                                    fs.Position = fs.Length - MarkLength;
                                    var buffer = new byte[MarkLength];
                                    fs.Read(buffer, 0, buffer.Length);
                                    string str = Encoding.Default.GetString(buffer);
                                    var arr = str.Split('-');
                                    if (arr.Length == 2)
                                    {
                                        long totalPackCount = 0;
                                        //这里验证 包的总个数 是否一致，如果不一致，则表示是不可用的 数据
                                        if (long.TryParse(arr[1], out totalPackCount) && totalPackCount == this.PackCount)
                                        {

                                            long posIndex = 0;

                                            if (long.TryParse(arr[0], out posIndex) && ((posIndex + 1) * PackSize + MarkLength) == TgtFileInfo.Length)
                                            {
                                                //上次已经写入内容，从 上次位置 开始
                                                _CurrPackPosIndex = posIndex;
                                                return _CurrPackPosIndex;
                                            }
                                            else
                                            {
                                                _CurrPackPosIndex = 0;
                                                return _CurrPackPosIndex;
                                            }

                                        }
                                        else
                                        {
                                            _CurrPackPosIndex = 0;
                                            return _CurrPackPosIndex;
                                        }
                                    }
                                    else
                                    {
                                        _CurrPackPosIndex = 0;
                                        return _CurrPackPosIndex;
                                    }
                                }
                                catch (Exception ex)
                                {
                                    this.OnError?.Invoke(this, new OnErrorEventArgs(nameof(CurrPackPosIndex), ex, this.UserState));
                                    _CurrPackPosIndex = 0;
                                    return _CurrPackPosIndex;
                                }
                                finally
                                {
                                    fs?.Close();
                                    fs?.Dispose();
                                }
                            }
                        }
                    }
                }
                return _CurrPackPosIndex;
            }
            set
            {
                _CurrPackPosIndex = value;
            }
        }

        private void RunWorkerAsync()
        {
            this.TFTask = Task.Run(() =>
            {
                if (!this.SrcFileInfo.Exists)
                {
                    this.State = State.出错;
                    this.OnError?.Invoke(this, new OnErrorEventArgs(nameof(RunWorkerAsync), new ArgumentException(this.SrcFileInfo.FullName + "文件不存在！", "srcPath"), this.UserState));
                    return;
                }

                this.State = State.执行中;
                this.IsBusy = true;
                try
                {
                    using (FileStream srcFS = new FileStream(this.SrcFileInfo.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
                    {
                        using (FileStream tgtFS = new FileStream(this.TgtFileInfo.FullName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
                        {
                            for (; this.CurrPackPosIndex < this.PackCount; this._CurrPackPosIndex++)
                            {
                                // 非 执行中 则不执行
                                if (this.State != State.执行中)
                                {
                                    return;
                                }

                                if (this._CurrPackPosIndex == this.PackCount - 1)
                                {
                                    //最后一个分片
                                }

                                if (_CurrPackPosIndex <= -1)
                                {
                                    _CurrPackPosIndex = 0;
                                }

                                //当前位置长度-总共长度
                                var strPos = GeneratePos(this._CurrPackPosIndex);

                                //本次设定 文件尾 分片位置信息
                                var posBuff = Encoding.Default.GetBytes(strPos);

                                //本次 传递的 分片数据
                                int bfLength = (int)Math.Min(PackSize, this.FileLength - this._CurrPackPosIndex * PackSize);

                                //写入的数据 = 分片数据 + 分片位置信息
                                byte[] buffer = new byte[bfLength + posBuff.Length];

                                //从位置信息 开始
                                srcFS.Position = _CurrPackPosIndex * PackSize;

                                //读取时 只读分片数据
                                srcFS.Read(buffer, 0, bfLength);

                                //将 分片位置信息 填充到 同一个字节数组buffer
                                Buffer.BlockCopy(posBuff, 0, buffer, bfLength, posBuff.Length);

                                //从什么位置读，就从什么位置写，不过尾部会增加 分片位置信息（每次写入会替换上一次写入的 分片位置信息）
                                tgtFS.Position = _CurrPackPosIndex * PackSize;

                                //分片数据 + 分片位置信息 同时写入
                                tgtFS.Write(buffer, 0, buffer.Length);

                                //检测 暂停 或 取消
                                //if (this.State == State.暂停 || this.State == State.取消)
                                if (this.State != State.执行中)
                                {
                                    return;
                                }
                                else
                                {
                                    //报告进度
                                    this.OnUpdateProgress?.Invoke(this, new OnUpdateProgressEventArgs(this.UserState, this.CurrRate));
                                }
                            }

                            var diffLen = tgtFS.Length - srcFS.Length;
                            if (diffLen != 0 && diffLen != MarkLength)
                            {
                                //判断是否 是 多出来 MarkLength 个字节
                            }

                            //最后 1个 多出的  分片位置信息 将会截除掉
                            tgtFS.SetLength(srcFS.Length);

                            //tgtFS.Flush();
                        }
                    }

                    if (this.State == State.执行中)
                    {
                        //进行md5验证前，再通报一次进度
                        this.OnUpdateProgress?.Invoke(this, new OnUpdateProgressEventArgs(this.UserState, this.CurrRate));

                        bool? isOK = null;
                        if (this.Md5Check)
                        {
                            this.State = State.校验文件;
                            this.OnMd5Checking?.Invoke(this, new OnMd5CheckingEventArgs(this.UserState));

                            try
                            {
                                isOK = Md5Compare();

                                if (!isOK.Value)
                                {
                                    // 验证失败？
                                }
                            }
                            catch (Exception ex)
                            {
                                this.State = State.出错;
                                this.OnError?.Invoke(this, new OnErrorEventArgs(nameof(RunWorkerAsync), ex, this.UserState));
                                return;
                            }
                        }

                        //因为 验证文件需要时间，这里再验证一次
                        if (this.State != State.取消 && this.State != State.暂停)
                        {
                            this.State = State.完成;
                        }

                        if (!this.IsCopy)
                        {
                            //删除文件，防止线程占用 删除文件失败的情况
                            //while (true)
                            {
                                try
                                {
                                    if (SrcFileInfo.Exists)
                                    {
                                        SrcFileInfo.Delete();
                                    }
                                    //break;
                                }
                                catch (Exception ex)
                                {
                                    this.OnError?.Invoke(this, new OnErrorEventArgs(nameof(RunWorkerAsync), ex, this.UserState));
                                    Thread.Sleep(500);
                                }
                            }
                        }
                        this.OnCompleted?.Invoke(this, new OnCompletedEventArgs(this.UserState, isOK));
                    }
                }
                catch (Exception ex)
                {
                    this.State = State.出错;
                    this.OnError?.Invoke(this, new OnErrorEventArgs(nameof(RunWorkerAsync), ex, this.UserState));
                    return;
                }
                finally
                {
                    this.IsBusy = false;
                }
            });
        }

        #region private 方法


        /// <summary>
        /// 删除目标文件
        /// </summary>
        private void DeleteOver()
        {
            //删除文件，防止线程占用 删除文件失败的情况
            //while (true)
            {
                try
                {
                    if (TgtFileInfo.Exists)
                    {
                        Thread.Sleep(1000);
                        TgtFileInfo.Delete();
                    }
                    //break;
                }
                catch
                {
                    Thread.Sleep(500);
                }
            }
        }


        /// <summary>
        /// md5比对文件
        /// </summary>
        /// <returns></returns>
        private bool Md5Compare()
        {
            return Validator.IsSame();
        }



        #endregion
    }
}
